package com.xingxue.util;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;

public abstract class DigestUtil {

	private static final char[] HEX = "0123456789abcdef".toCharArray();

	private static final Random RANDOM = new Random();

	private static final int SALT_LENGTH = 8;

	private static final char[] SALT = "0123456789abcdef".toCharArray();

	public static String toHex(byte[] digest) {
		StringBuilder sb = new StringBuilder(32);
		for (int i = 0; i < digest.length; i++) {
			byte b = digest[i];
			sb.append(HEX[b >> 4 & 0xf]);
			sb.append(HEX[b & 0xf]);
		}
		return sb.toString();
	}

	/**
	 * 产生一个随机的盐
	 * <br>
	 * 一般的实现里，使用字节数组，这里为了看得清楚，使用了字符串的盐
	 * @return
	 */
	public static String randomSalt() {
		StringBuilder sb = new StringBuilder(SALT_LENGTH);
		for (int i = 0; i < SALT_LENGTH; i++) {
			sb.append(SALT[RANDOM.nextInt(SALT.length)]);
		}
		return sb.toString();
	}

	/**
	 * 将密文与盐再混淆
	 * <br>
	 * 这么做的好处是，不用单独的一列存储盐，保证盐不被别人轻易看到
	 * <br>
	 * 混淆规则应该有一定复杂度，不要让别人轻易猜到
	 * <br>
	 * 这里的规则是，加密密码和盐逐个字符交错
	 * <br> 比如，加密后密码是  abcd，盐是0123，那么结果将是 a0b1c2d3
	 * @param encryptPass 加密后密码
	 * @param salt  盐
	 * @return 混淆后存入数据库的密码
	 */
	public static String mixup(String encryptPass, String salt) {
		StringBuilder sb = new StringBuilder(encryptPass.length() + salt.length());
		if (encryptPass.length() > salt.length()) {
			int i = 0;
			while (i < salt.length()) {
				sb.append(encryptPass.charAt(i));
				sb.append(salt.charAt(i));
				i++;
			}
			sb.append(encryptPass.substring(i, encryptPass.length()));
		} else {
			int i = 0;
			while (i < encryptPass.length()) {
				sb.append(encryptPass.charAt(i));
				sb.append(salt.charAt(i));
				i++;
			}
			sb.append(salt.substring(i, salt.length()));
		}
		return sb.toString();
	}

	/**
	 * 加密
	 * @param plainPass 明文密码
	 * @param salt 盐
	 * @return 加密后密码
	 */
	public static String encrypt(String plainPass, String salt) {
		try {
			MessageDigest md = MessageDigest.getInstance("md5");
			md.update(plainPass.getBytes());
			md.update(salt.getBytes());
			String encryptPass = toHex(md.digest());
			return encryptPass;
		} catch (NoSuchAlgorithmException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 加密并将密文与盐再混淆
	 * @param plainPass 明文密码
	 * @param salt 盐
	 * @return 混淆后存入数据库的密码
	 */
	public static String encryptAndMixup(String plainPass, String salt) {
		String encryptPass = encrypt(plainPass, salt);
		return mixup(encryptPass, salt);
	}

	/**
	 * 校验密码是否正确
	 * @param plainPass 明文密码
	 * @param dbPass 数据库密码（与盐混淆过）
	 * @return true表示密码校验通过, 反之不通过
	 */
	public static boolean isValid(String plainPass, String dbPass) {
		System.out.printf("[调试信息] 存入数据库的密码是: \t%s\n", dbPass);
		StringBuilder salt = new StringBuilder(SALT_LENGTH);
		StringBuilder encryptPass = new StringBuilder(dbPass.length() - SALT_LENGTH);
		for (int i = 0; i < dbPass.length(); i++) {
			if (i % 2 == 1 && salt.length() < 8) {
				salt.append(dbPass.charAt(i));
			} else {
				encryptPass.append(dbPass.charAt(i));
			}
		}
		System.out.printf("[调试信息] 盐是(db): \t%s\n", salt);
		System.out.printf("[调试信息] 加密后的密码是(db): \t%s\n", encryptPass);
		String s = encrypt(plainPass, salt.toString());
		return s.equals(encryptPass.toString());
	}

}
